package ssq.gamest.game;

import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Vector;

import ssq.utils.Pair;

/**
 * 几张扑克牌. 提供按点数和花色分组和比较大小的方法.
 *
 * @author s
 *
 */
public class PokerCardGroup extends LinkedList<PokerCard> implements Comparable<PokerCardGroup>
{
    private static final long                                  serialVersionUID = -8080558096523130122L;
    public final Poker                                         game;
    /**
     * 按从大到小的顺序保存所有符合的牌型. 需要时才计算
     */
    private List<Pair<Class<? extends PokerPattern>, Integer>> matchedPatterns  = new Vector<Pair<Class<? extends PokerPattern>, Integer>>();

    public PokerCardGroup(Poker game, PokerCard... cards)
    {
        super(Arrays.asList(cards));
        this.game = game;
        Collections.sort(this);
    }

    public PokerCardGroup(PokerCardGroup g)
    {
        super(g);
        this.game = g.game;
    }

    @Override
    public Object clone()
    {
        return new PokerCardGroup(this);
    }

    /**
     * 判断这几张扑克牌是否包含指定条件的子牌型. <br/>
     * 点数递增是隐含的:<br/>
     * 若point!=0, 而且r非1, 则认为点数递增, 否则不必分两组写<br/>
     * 若point==0, 则l代表相同的任意点数的牌的张数, r表示组数.<br/>
     * 无论判断结果, 本对象不变
     *
     * @param l
     *            左系数, 没有时应为1而非0
     * @param point
     *            点数1-15, 0表示任意点数
     * @param color
     *            颜色4种, none表示任意颜色
     * @param byOrder
     *            按大小顺序而非点数的字面顺序处理递增, 若非递增, 则本参数无效
     * @param r
     *            右系数, 没有时应为1而非0
     * @param upperBound
     *            上界, 要求主调函数将上下界处理好再调用, 若不存在上界, 设置为-1
     * @param lowerBound
     *            下界 若不存在下界, 设置为-1
     * @return 若这几张扑克牌包含由参数指定的子牌型, 则返回按排序先后去掉这个子牌型后的几张牌, 否则返回null
     */
    public PokerCardGroup contains(int l, int point, Color color, boolean byOrder, int r, int lowerBound, int upperBound)
    {
        if (r <= 0 || l <= 0)
        {
            return (PokerCardGroup) clone();
        }

        int colorFactor;
        int colorOffset;

        boolean withColor = color != null;

        if (withColor)
        {
            colorFactor = 5;
            colorOffset = color.ordinal();
        }
        else
        {
            colorFactor = 1;
            colorOffset = 0;
        }

        boolean isAny = point == 0;
        PokerCardGroup result = (PokerCardGroup) clone();

        if (isAny)
        {
            for (int j = 0; j < r; j++)
            {
                LinkedList<PokerCard>[] cardsOfNumber = result.divideByNumber(withColor);
                boolean found = false;
                for (int i = colorFactor * l + colorOffset - colorFactor; i < cardsOfNumber.length; i += colorFactor)
                {
                    LinkedList<PokerCard> cards = cardsOfNumber[i];

                    if (cards != null && !cards.isEmpty())
                    {
                        PokerCard first = cards.getFirst();
                        result.remove(first.point, withColor ? first.color : null, l);
                        found = true;
                        break;
                    }
                }

                if (!found)
                {
                    return null;
                }
            }

            return result;
        }

        boolean incre = point != 0 && r != 1;

        Vector<Integer> toBeScanned = game.settings.getScanList(incre && byOrder);
        int begin = lowerBound == -1 ? 0 : toBeScanned.indexOf(lowerBound), end = upperBound == -1 ? toBeScanned.size() - 1 : toBeScanned.lastIndexOf(upperBound);
        int indexOfPoint = toBeScanned.indexOf(point, begin);

        if (indexOfPoint < 0)
        {
            return null;
        }

        if (indexOfPoint + r - 1 > end)
        {
            return null;
        }

        for (int cnt = indexOfPoint; cnt < indexOfPoint + r; cnt++)
        {
            int[] numOfPoints = result.divideByPoint(withColor);
            int i = toBeScanned.get(cnt);
            int index = (i - 1) * colorFactor + colorOffset;

            if (numOfPoints[index] >= l)
            {
                result.remove(i, withColor ? color : null, l);
            }
            else
            {
                return null;
            }
        }
        return result;
    }

    public LinkedList<PokerCard>[] divideByNumber()
    {
        return divideByNumber(game.settings.withColor);
    }

    /**
     * 将这组牌按几张分组.
     *
     * @return 在withColor为false的情况下: 同样点数的有n张则在下标n-1处加入这个点数. <br/>
     *         否则, withColor为true, 同样点数和花色的有n张则在下标n-1处加入这个点数.
     *
     * @see ssq.gamest.game.PokerOrderSettings.withColor
     */
    @SuppressWarnings("unchecked")
    public LinkedList<PokerCard>[] divideByNumber(boolean withColor)
    {
        LinkedList<PokerCard>[] result = new LinkedList[size() * (withColor ? 5 : 1)];

        int cnt = 0;
        int lastPoint = 0;
        Color lastColor = Color.none;
        for (PokerCard card : this)
        {
            if (card.point != lastPoint || withColor && card.color != lastColor)
            {
                _(withColor, result, cnt, lastPoint, lastColor);
                cnt = 1;
            }
            else
            {
                cnt++;
            }

            lastPoint = card.point;
            lastColor = card.color;
        }

        _(withColor, result, cnt, lastPoint, lastColor);
        return result;
    }

    public int[] divideByPoint(boolean withColor)
    {
        return withColor ? divideByPointAndColor() : divideByPoint();
    }

    /**
     * 将这组牌按点数分组.
     *
     * @return n点的牌有m张, 则result[n-1]=m
     *
     */
    public int[] divideByPoint()
    {
        int[] result = new int[15];

        int cnt = 0;
        int lastPoint = 0;
        for (PokerCard card : this)
        {
            if (card.point != lastPoint)
            {
                _(result, cnt, lastPoint);
                cnt = 1;
            }
            else
            {
                cnt++;
            }

            lastPoint = card.point;
        }

        _(result, cnt, lastPoint);
        return result;
    }

    /**
     * 将这组牌按点数和花色分组.
     *
     * @return 在ssq.gamest.game.PokerOrderSettings.withColor为false的情况下: <br/>
     *         同样点数的有n张则在下标n-1处加入这个点数. <br/>
     *         否则, withColor为true, 同样点数和花色的有n张则在下标n-1处加入这个点数.
     *
     * @see ssq.gamest.game.PokerOrderSettings.withColor
     */
    public int[] divideByPointAndColor()
    {
        final boolean withColor = true;

        int[] result = new int[75];

        int cnt = 0;
        int lastPoint = 0;
        Color lastColor = Color.none;
        for (PokerCard card : this)
        {
            if (card.point != lastPoint || withColor && card.color != lastColor)
            {
                _(result, cnt, lastPoint, lastColor);
                cnt = 1;
            }
            else
            {
                cnt++;
            }

            lastPoint = card.point;
            lastColor = card.color;
        }

        _(result, cnt, lastPoint, lastColor);
        return result;
    }

    /**
     * 删除指定点数花色的张数的牌, 供删除的牌多余要删除的张数时, 按order顺序. 删完之后还是有序的, 不需要重新排序.
     *
     * @param point
     *            点数. point<=0时不考虑点数
     * @param color
     *            花色. color==null时不考虑花色
     * @param num
     *            张数
     */
    public boolean remove(int point, Color color, int num)
    {
        try
        {
            for (Iterator<PokerCard> iterator = this.iterator(); iterator.hasNext() && num > 0;)
            {
                PokerCard pokerCard = iterator.next();
                if ((point <= 0 || pokerCard.point == point) && (color == null || pokerCard.color.equals(color)))
                {
                    iterator.remove();
                    num--;
                }
            }

            return true;
        }
        catch (Exception e)
        {
            return false;
        }
    }
    
    /**
     * 检查是否是某牌型
     *
     * @param pattern
     * @return
     */
    public int match(PokerPattern pattern)
    {
        List<Pair<PokerPattern, PokerCardGroup>> result = pattern.match(this, false, true);

        return result.size() < 1 ? -1 : 1;
    }

    /**
     * 把所有可以match的全部遍历
     *
     * @return
     */
    public int matchAll()
    {
        matchedPatterns.clear();

        Vector<Class<? extends PokerPattern>> idPattern = game.settings.idPattern;

        for (int i = 0; i < idPattern.size(); i++)
        {
            Class<? extends PokerPattern> clazz = idPattern.get(i);
            try
            {
                int result = match(clazz.newInstance());
                if (result >= 0)
                {
                    matchedPatterns.add(new Pair<Class<? extends PokerPattern>, Integer>(clazz, result));
                }
            }
            catch (Exception e)
            {
                e.printStackTrace();
            }
        }

        return matchedPatterns.size();
    }

    /**
     * 牌组的大小是偏序关系, 若无法比较大小, 返回-2.
     */
    @Override
    public int compareTo(PokerCardGroup another)
    {
        Vector<Class<? extends PokerPattern>> idPattern = game.settings.idPattern;

        for (int i = 0; i < idPattern.size(); i++)
        {
            Class<? extends PokerPattern> clazz = idPattern.get(i);
            try
            {
                int result = match(clazz.newInstance());
                if (result >= 0)
                {
                    matchedPatterns.add(new Pair<Class<? extends PokerPattern>, Integer>(clazz, result));
                }
            }
            catch (Exception e)
            {
                e.printStackTrace();
            }
        }

        return -2;
    }
    
    private void _(int[] result, int cnt, int point, Color color)
    {
        if (cnt > 0)
        {
            int index = (point - 1) * 5 + color.ordinal();

            result[index] = cnt;
        }
    }
    
    private void _(int[] result, int cnt, int point)
    {
        if (cnt > 0)
        {
            int index = point - 1;

            result[index] = cnt;
        }
    }
    
    private void _(boolean withColor, LinkedList<PokerCard>[] result, int cnt, int lastPoint, Color lastColor)
    {
        if (cnt > 0)
        {
            int index = withColor ? (cnt - 1) * 5 + lastColor.ordinal() : cnt - 1;

            if (result[index] == null)
            {
                result[index] = new LinkedList<PokerCard>();
            }
            result[index].add(new PokerCard(lastPoint, lastColor, this.game));
        }
    }
}
